43. 数据结构之字典

什么是字典?

Python 字典(Dict) 是一种基于键值对的数据结构:

  • 每个键(Key) 唯一对应一个值(Value)
  • 底层基于哈希表(Hash Table) 实现
  • 查找速度极快:平均时间复杂度 O(1)
  • Python 3.7+ 字典保持插入顺序
# 键值对格式: {键: 值}
stock = {'代码': '600519.SH', '名称': '贵州茅台', '价格': 1850}

字典的四大核心特性

特性 说明 应用场景
键唯一性 每个键只能出现一次 股票代码映射
快速查找 哈希计算,O(1)平均 实时行情查询
有序性 Python 3.7+保持插入顺序 时间序列索引
异构性 键值可为不同类型 多维度数据存储

为什么字典查找比列表快?

假设有1000只股票需要查找:

  • 列表查找:平均需要 500 次比较 → O(n)
  • 字典查找:只需 1 次哈希计算 → O(1)

性能差异可达 500倍

这正是高频交易系统使用字典存储订单簿的原因。

字典时间复杂度一览

操作 平均情况 最坏情况 说明
插入 O(1) O(n) 负载因子 < 0.66 时
查找 O(1) O(n) 同上
删除 O(1) O(n) 同上

Python 字典保持负载因子低于 2/3,确保平均性能为 O(1)。

⭐ 平台任务解答代码

Listing 1
# 注:该代码块包含未完成的填空代码,需要在平台上完成
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
dict1 = {"汇率变量":"美元兑人民币","日期":"2024-08-30","中间价":"7.1124","涨跌幅":"-0.0025"}  
dict2 = {"汇率变量":"欧元兑人民币","日期":"2024-08-21","中间价":"7.9325","涨跌幅":"0.0038"}  # 定义字典dict2
dict3 = {"汇率变量":"港元兑人民币","日期":"2024-08-19","中间价":"0.9162","涨跌幅":"-0.0003"}  # 定义字典dict3
dict4 = {"汇率变量":"英镑兑人民币","日期":"2024-08-08","中间价":"9.0701","涨跌幅":"0.0011"}  # 定义字典dict4

print(dict1.keys()) #输出字典dict1的全部键码

print(dict2.values()) #输出字典dict2的全部数值

print(dict3.items())  #输出字典dict3的全部元素


#任务二
dict1 = {"汇率变量":"美元兑人民币","日期":"2024-08-30","中间价":"7.1124","涨跌幅":"-0.0025"}  
dict2 = {"汇率变量":"欧元兑人民币","日期":"2024-08-21","中间价":"7.9325","涨跌幅":"0.0038"}  # 定义字典dict2
dict4 = {"汇率变量":"英镑兑人民币","日期":"2024-08-08","中间价":"9.0701","涨跌幅":"0.0011"}  # 定义字典dict4
 
print(dict1["日期"])   #查询美元兑人民币汇率的具体日期
 
print(dict2["中间价"])  #查询欧元兑人民币汇率的中间价
 
print(dict4["涨跌幅"])   #查询英镑兑人民币的涨跌幅
 
#任务三
dict2 = {"汇率变量":"欧元兑人民币","日期":"2024-08-21","中间价":"7.9325","涨跌幅":"0.0038"}
 
dict2["日期"] = "2024-08-29"           #修改字典2新的日期

dict2["中间价"] = 7.9318               #修改字典2新的中间价

dict2["涨跌幅"] = -0.0037              #修改字典2新的涨跌幅

print(dict2)  # 输出变量dict2的值

#任务四
dict3 = {"汇率变量":"港元兑人民币","日期":"2024-08-19","中间价":"0.9162","涨跌幅":"-0.0003"}

#使用update函数在dict3中添加"前一日中间价":"0.9165","前一日涨跌幅":"0.0002"
dict3.update({"前一日中间价":"0.9165","前一日涨跌幅":"0.0002"})   
print(dict3)  # 输出任务四
 
dict4 = {"汇率变量":"英镑兑人民币","日期":"2024-08-08","中间价":"9.0701","涨跌幅":"0.0011"}  # 定义字典dict4
del dict4["涨跌幅"]           #删除dict4的涨跌幅
print(dict4)  # 输出变量dict4的值

字典的创建:字面量语法

使用花括号 {} 直接创建字典,最常用、最推荐的方式:

Listing 2
# 花括号{}是字典的字面量语法
# 键值对格式: 键:值,使用冒号分隔
stock_codes = {
    '600': '上证A股',
    '900': '上证B股',
    '000': '深证A股',
    '200': '深证B股',
    '400': '三板市场股票'
}
print('股票代码分类:', stock_codes)
股票代码分类: {'600': '上证A股', '900': '上证B股', '000': '深证A股', '200': '深证B股', '400': '三板市场股票'}

字典的创建:构造函数与批量创建

Listing 3
# 方式2: dict()构造函数 —— 先创建空字典再逐个添加
stock_codes_b = dict()
stock_codes_b['600'] = '上证A股'
stock_codes_b['900'] = '上证B股'
print('构造函数:', stock_codes_b)

# 方式3: dict.fromkeys()批量创建 —— 所有值相同时使用
codes = ['600', '900', '000']
stock_codes_c = dict.fromkeys(codes, '其他')
print('fromkeys:', stock_codes_c)
构造函数: {'600': '上证A股', '900': '上证B股'}
fromkeys: {'600': '其他', '900': '其他', '000': '其他'}

字典操作:访问元素

Listing 4
# 创建股票代码到名称的映射
stock_dict = {
    '600519.SH': '贵州茅台',
    '000858.SZ': '五粮液',
    '600036.SH': '招商银行'
}

# 推荐使用 get()方法 —— 键不存在时返回默认值,不报错
print('查询600519.SH:', stock_dict.get('600519.SH', '未知'))

# 查询不存在的键 —— 安全返回默认值
print('查询999999.XX:', stock_dict.get('999999.XX', '未知'))
# 如果用 stock_dict['999999.XX'] 会报 KeyError!
查询600519.SH: 贵州茅台
查询999999.XX: 未知

字典操作:添加与删除

Listing 5
stock_dict = {
    '600519.SH': '贵州茅台',
    '000858.SZ': '五粮液',
    '600036.SH': '招商银行'
}

# 添加新键值对(键已存在则覆盖)
stock_dict['601318.SH'] = '中国平安'
print('添加后:', stock_dict)

# del 删除键值对(键不存在会报 KeyError)
del stock_dict['600036.SH']
print('删除后:', stock_dict)
添加后: {'600519.SH': '贵州茅台', '000858.SZ': '五粮液', '600036.SH': '招商银行', '601318.SH': '中国平安'}
删除后: {'600519.SH': '贵州茅台', '000858.SZ': '五粮液', '601318.SH': '中国平安'}

字典操作:获取键、值与大小

Listing 6
stock_dict = {
    '600519.SH': '贵州茅台',
    '000858.SZ': '五粮液',
    '601318.SH': '中国平安'
}

# keys() 返回所有键
print('键:', list(stock_dict.keys()))

# values() 返回所有值
print('值:', list(stock_dict.values()))

# items() 返回所有键值对
print('项:', list(stock_dict.items()))

# len() 返回键值对数量
print('项数:', len(stock_dict))
键: ['600519.SH', '000858.SZ', '601318.SH']
值: ['贵州茅台', '五粮液', '中国平安']
项: [('600519.SH', '贵州茅台'), ('000858.SZ', '五粮液'), ('601318.SH', '中国平安')]
项数: 3

安全删除:pop() 方法

Listing 7
stock_dict = {
    '600519.SH': '贵州茅台',
    '000858.SZ': '五粮液'
}

# pop():键不存在时返回默认值,不会报错
removed = stock_dict.pop('999999.XX', None)
print('删除不存在的键:', removed)  # None

# pop():键存在时删除并返回值
removed = stock_dict.pop('000858.SZ', None)
print('删除的值:', removed)  # 五粮液
print('剩余字典:', stock_dict)
删除不存在的键: None
删除的值: 五粮液
剩余字典: {'600519.SH': '贵州茅台'}

批量更新:update() 方法

Listing 8
stock_dict = {'600519.SH': '贵州茅台'}

# update():批量添加/更新键值对
new_stocks = {
    '000001.SZ': '平安银行',
    '601318.SH': '中国平安'
}
stock_dict.update(new_stocks)
print('更新后:', stock_dict)
# 键已存在 → 更新值;键不存在 → 添加新键值对
更新后: {'600519.SH': '贵州茅台', '000001.SZ': '平安银行', '601318.SH': '中国平安'}

字典的遍历:三种方式

Listing 9
prices = {'贵州茅台': 1850, '五粮液': 220, '招商银行': 45}

# 方式1: 遍历键
print('遍历键:')
for key in prices:
    print(f'  {key}')

# 方式2: 遍历键值对(推荐)
print('\n遍历键值对:')
for key, value in prices.items():
    print(f'  {key}: {value}元')

# 方式3: 遍历值
print('\n遍历值:')
for value in prices.values():
    print(f'  {value}元')
遍历键:
  贵州茅台
  五粮液
  招商银行

遍历键值对:
  贵州茅台: 1850元
  五粮液: 220元
  招商银行: 45元

遍历值:
  1850元
  220元
  45元

字典推导式

Listing 10
prices = {'贵州茅台': 1850, '五粮液': 220, '招商银行': 45}

# 语法: {键表达式: 值表达式 for item in iterable}

# 应用1: 反转键值对
inverted = {v: k for k, v in prices.items()}
print('反转字典:', inverted)

# 应用2: 过滤 —— 只保留价格 > 100 的股票
expensive = {k: v for k, v in prices.items() if v > 100}
print('高价股:', expensive)

# 应用3: 转换 —— 价格转为万元
prices_wan = {k: v / 10000 for k, v in prices.items()}
print('万元价格:', prices_wan)
反转字典: {1850: '贵州茅台', 220: '五粮液', 45: '招商银行'}
高价股: {'贵州茅台': 1850, '五粮液': 220}
万元价格: {'贵州茅台': 0.185, '五粮液': 0.022, '招商银行': 0.0045}

金融应用:股票代码查询

Listing 11
# 股票代码到名称的映射(查找表)
code_to_name = {
    '600519.SH': '贵州茅台',
    '000858.SZ': '五粮液',
    '600036.SH': '招商银行',
    '601318.SH': '中国平安',
    '000001.SZ': '平安银行'
}

# 批量查询
codes_to_query = ['600519.SH', '000858.SZ', '000001.SZ']
print('股票查询结果:')
for code in codes_to_query:
    name = code_to_name.get(code, '未知股票')
    print(f'  {code}: {name}')
股票查询结果:
  600519.SH: 贵州茅台
  000858.SZ: 五粮液
  000001.SZ: 平安银行

金融应用:反向映射

Listing 12
code_to_name = {
    '600519.SH': '贵州茅台',
    '000858.SZ': '五粮液',
    '601318.SH': '中国平安',
    '000001.SZ': '平安银行'
}

# 使用字典推导式交换键和值
name_to_code = {v: k for k, v in code_to_name.items()}

print('平安银行代码:', name_to_code.get('平安银行'))
print('腾讯代码:', name_to_code.get('腾讯'))  # None
平安银行代码: 000001.SZ
腾讯代码: None

金融应用:投资组合权重计算

Listing 13
# 持仓价值(元)
holdings = {
    '贵州茅台': 185000,
    '五粮液': 44000,
    '招商银行': 22500
}

total = sum(holdings.values())

# 计算权重
weights = {stock: value / total for stock, value in holdings.items()}

print('持仓权重:')
for stock, weight in sorted(weights.items(), key=lambda x: -x[1]):
    print(f'  {stock}: {weight:.2%}')
持仓权重:
  贵州茅台: 73.56%
  五粮液: 17.50%
  招商银行: 8.95%

嵌套字典:多维度数据存储

Listing 14
stock_info = {
    '600519.SH': {
        'name': '贵州茅台',
        'price': 1850.00,
        'pe': 45.2,
        'market_cap': 2325000000000
    },
    '000858.SZ': {
        'name': '五粮液',
        'price': 220.50,
        'pe': 32.8,
        'market_cap': 856000000000
    }
}

# 访问嵌套信息
maotai = stock_info['600519.SH']
print(f'{maotai["name"]}: PE={maotai["pe"]}, 市值={maotai["market_cap"]/1e8:.0f}亿元')
贵州茅台: PE=45.2, 市值=23250亿元

defaultdict:自动初始化的字典

Listing 15
from collections import defaultdict

# 使用 defaultdict 避免手动检查键是否存在
# 当访问不存在的键时,自动创建默认值
counter = defaultdict(int)  # 默认值为 0

# 统计股票出现次数
codes = ['600519.SH', '000858.SZ', '600519.SH', '600519.SH']
for code in codes:
    counter[code] += 1  # 即使 code 不存在也不会报错

print(dict(counter))
{'600519.SH': 3, '000858.SZ': 1}

字典 vs 列表:如何选择?

根据数据访问需求选择合适的数据结构:

场景 推荐 原因
通过标识符查找数据 字典 O(1) 哈希查找
按顺序访问数据 列表 支持索引和切片
需要去重 集合 自动去重
存储键值关系 字典 天然键值对结构
需要频繁增删 视情况 列表尾部操作快

本章小结

  • 字典是基于哈希表的键值对数据结构,查找性能 O(1)
  • 创建方式:{} 字面量(推荐)、dict() 构造函数、fromkeys()
  • 安全访问用 get(),安全删除用 pop()
  • 三种遍历:keys()values()items()
  • 字典推导式可以高效创建、过滤和转换字典
  • 金融应用:股票代码映射、投资组合计算、嵌套数据存储